/*
 * 程序名：tcpputfiles.cpp，采用tcp协议，实现文件上传的客户端。
 * 作者：吴从周。
 */

/*
/wanyingxing/project/tools1/c2/output/tcpputfiles /wanyingxing/project/log/tcpput.log "<ip>127.0.0.1</ip><port>5005</port><ptype>2</ptype><clientpath>/wanyingxing/project/tcpClient/tcpClientPutDir</clientpath><clientpathbak>/wanyingxing/project/tcpClient/tcpClientPutBakDir</clientpathbak><andchild>true</andchild><matchname>*.XML,*.CSV</matchname><srvpath>/wanyingxing/project/tcpServer/tcpServerGetClientDir</srvpath><timetvl>10</timetvl><timeout>50</timeout><pname>tcpputfiles_surfdata</pname>"
*/
#include "_public.h"

// 程序运行的参数结构体。
struct st_arg
{
  int clienttype;          // 客户端类型，1-上传文件；2-下载文件。
  char ip[31];             // 服务端的IP地址。
  int port;                // 服务端的端口。
  int ptype;               // 文件上传成功后文件的处理方式：1-删除文件；2-移动到备份目录。
  char clientpath[301];    // 本地文件存放的根目录。
  char clientpathbak[301]; // 文件成功上传后，本地文件备份的根目录，当ptype==2时有效。
  bool andchild;           // 是否上传clientpath目录下各级子目录的文件，true-是；false-否。
  char matchname[301];     // 待上传文件名的匹配规则，如"*.TXT,*.XML"。
  char srvpath[301];       // 服务端文件存放的根目录。
  int timetvl;             // 扫描本地目录文件的时间间隔，单位：秒。
  int timeout;             // 进程心跳的超时时间。
  char pname[51];          // 进程名，建议用"tcpputfiles_后缀"的方式。
} starg;

CLogFile logfile;

// 程序退出和信号2、15的处理函数。
void EXIT(int sig);

void _help();

// 把xml解析到参数starg结构中。
bool _xmltoarg(char *strxmlbuffer);

CTcpClient TcpClient;

bool Login(const char *argv); // 登录业务。

bool ActiveTest(); // 心跳。

bool _tcpputfiles();
bool SendFile(const int sockfd, const char *filename, const int filesize);
bool AckMessage(const char *strrecvbuffer);

char strrecvbuffer[1024]; // 发送报文的buffer。
char strsendbuffer[1024]; // 接收报文的buffer。

CPActive PActive; // 进程心跳。

int main(int argc, char *argv[])
{
  if (argc != 3)
  {
    _help();
    return -1;
  }

  // 关闭全部的信号和输入输出。
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
  // 但请不要用 "kill -9 +进程号" 强行终止。
  CloseIOAndSignal();
  signal(SIGINT, EXIT);
  signal(SIGTERM, EXIT);

  // 打开日志文件。
  if (logfile.Open(argv[1], "a+") == false)
  {
    printf("打开日志文件失败（%s）。\n", argv[1]);
    return -1;
  }

  // 解析xml，得到程序运行的参数。
  if (_xmltoarg(argv[2]) == false)
    return -1;

  // PActive.AddPInfo(starg.timeout,starg.pname);  // 把进程的心跳信息写入共享内存。

  // 向服务端发起连接请求。
  if (TcpClient.ConnectToServer(starg.ip, starg.port) == false)
  {
    logfile.Write("TcpClient.ConnectToServer(%s,%d) failed.\n", starg.ip, starg.port);
    EXIT(-1);
  }

  // 登录业务。
  if (Login(argv[2]) == false)
  {
    logfile.Write("Login() failed.\n");
    EXIT(-1);
  }

  while (true)
  {
    /* code */
    // 循环上传或者下载，或者心跳
    // 由于业务比较简单，所以并不需要加入约我码，服务端只要对前10个字节进行检测，就知道是什么业务

    _tcpputfiles();
    if (ActiveTest() == false)
      break;

    // sleep

    sleep(starg.timetvl);
  }
  EXIT(0);
}

// 心跳。
bool ActiveTest()
{
  memset(strsendbuffer, 0, sizeof(strsendbuffer));
  memset(strrecvbuffer, 0, sizeof(strrecvbuffer));

  SPRINTF(strsendbuffer, sizeof(strsendbuffer), "<activetest>ok</activetest>");
  logfile.Write("发送：%s\n", strsendbuffer);
  if (TcpClient.Write(strsendbuffer) == false)
    return false; // 向服务端发送请求报文。

  if (TcpClient.Read(strrecvbuffer, 20) == false)
    return false; // 接收服务端的回应报文。
  logfile.Write("接收：%s\n", strrecvbuffer);

  return true;
}

// 登录业务。
bool Login(const char *argv)
{
  memset(strsendbuffer, 0, sizeof(strsendbuffer));
  memset(strrecvbuffer, 0, sizeof(strrecvbuffer));

  SPRINTF(strsendbuffer, sizeof(strsendbuffer), "%s<clienttype>1</clienttype>", argv);
  logfile.Write("发送：%s\n", strsendbuffer);
  if (TcpClient.Write(strsendbuffer) == false)
    return false; // 向服务端发送请求报文。

  if (TcpClient.Read(strrecvbuffer, 20) == false)
    return false; // 接收服务端的回应报文。
  logfile.Write("接收：%s\n", strrecvbuffer);

  logfile.Write("登录(%s:%d)成功。\n", starg.ip, starg.port);

  return true;
}

void EXIT(int sig)
{
  logfile.Write("程序退出，sig=%d\n\n", sig);

  exit(0);
}

void _help()
{
  printf("\n");
  printf("Using:/project/tools1/bin/tcpputfiles logfilename xmlbuffer\n\n");

  printf("Sample:/project/tools1/bin/procctl 20 /project/tools1/bin/tcpputfiles /log/idc/tcpputfiles_surfdata.log \"<ip>192.168.174.132</ip><port>5005</port><ptype>1</ptype><clientpath>/tmp/tcp/surfdata1</clientpath><clientpathbak>/tmp/tcp/surfdata1bak</clientpathbak><andchild>true</andchild><matchname>*.XML,*.CSV</matchname><srvpath>/tmp/tcp/surfdata2</srvpath><timetvl>10</timetvl><timeout>50</timeout><pname>tcpputfiles_surfdata</pname>\"\n");
  printf("       /project/tools1/bin/procctl 20 /project/tools1/bin/tcpputfiles /log/idc/tcpputfiles_surfdata.log \"<ip>192.168.174.132</ip><port>5005</port><ptype>2</ptype><clientpath>/tmp/tcp/surfdata1</clientpath><clientpathbak>/tmp/tcp/surfdata1bak</clientpathbak><andchild>true</andchild><matchname>*.XML,*.CSV</matchname><srvpath>/tmp/tcp/surfdata2</srvpath><timetvl>10</timetvl><timeout>50</timeout><pname>tcpputfiles_surfdata</pname>\"\n\n\n");

  printf("本程序是数据中心的公共功能模块，采用tcp协议把文件发送给服务端。\n");
  printf("logfilename   本程序运行的日志文件。\n");
  printf("xmlbuffer     本程序运行的参数，如下：\n");
  printf("ip            服务端的IP地址。\n");
  printf("port          服务端的端口。\n");
  printf("ptype         文件上传成功后的处理方式：1-删除文件；2-移动到备份目录。\n");
  printf("clientpath    本地文件存放的根目录。\n");
  printf("clientpathbak 文件成功上传后，本地文件备份的根目录，当ptype==2时有效。\n");
  printf("andchild      是否上传clientpath目录下各级子目录的文件，true-是；false-否，缺省为false。\n");
  printf("matchname     待上传文件名的匹配规则，如\"*.TXT,*.XML\"\n");
  printf("srvpath       服务端文件存放的根目录。\n");
  printf("timetvl       扫描本地目录文件的时间间隔，单位：秒，取值在1-30之间。\n");
  printf("timeout       本程序的超时时间，单位：秒，视文件大小和网络带宽而定，建议设置50以上。\n");
  printf("pname         进程名，尽可能采用易懂的、与其它进程不同的名称，方便故障排查。\n\n");
}

// 把xml解析到参数starg结构
bool _xmltoarg(char *strxmlbuffer)
{
  memset(&starg, 0, sizeof(struct st_arg));

  GetXMLBuffer(strxmlbuffer, "ip", starg.ip);
  if (strlen(starg.ip) == 0)
  {
    logfile.Write("ip is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "port", &starg.port);
  if (starg.port == 0)
  {
    logfile.Write("port is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "ptype", &starg.ptype);
  if ((starg.ptype != 1) && (starg.ptype != 2))
  {
    logfile.Write("ptype not in (1,2).\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "clientpath", starg.clientpath);
  if (strlen(starg.clientpath) == 0)
  {
    logfile.Write("clientpath is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "clientpathbak", starg.clientpathbak);
  if ((starg.ptype == 2) && (strlen(starg.clientpathbak) == 0))
  {
    logfile.Write("clientpathbak is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "andchild", &starg.andchild);

  GetXMLBuffer(strxmlbuffer, "matchname", starg.matchname);
  if (strlen(starg.matchname) == 0)
  {
    logfile.Write("matchname is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "srvpath", starg.srvpath);
  if (strlen(starg.srvpath) == 0)
  {
    logfile.Write("srvpath is null.\n");
    return false;
  }

  GetXMLBuffer(strxmlbuffer, "timetvl", &starg.timetvl);
  if (starg.timetvl == 0)
  {
    logfile.Write("timetvl is null.\n");
    return false;
  }

  // 扫描本地目录文件的时间间隔，单位：秒。
  // starg.timetvl没有必要超过30秒。
  if (starg.timetvl > 30)
    starg.timetvl = 30;

  // 进程心跳的超时时间，一定要大于starg.timetvl，没有必要小于50秒。
  GetXMLBuffer(strxmlbuffer, "timeout", &starg.timeout);
  if (starg.timeout == 0)
  {
    logfile.Write("timeout is null.\n");
    return false;
  }
  if (starg.timeout < 50)
    starg.timeout = 50;

  GetXMLBuffer(strxmlbuffer, "pname", starg.pname, 50);
  if (strlen(starg.pname) == 0)
  {
    logfile.Write("pname is null.\n");
    return false;
  }

  return true;
}

bool _tcpputfiles()
{
  // 读取上传目录的所有的信息，发送给服务器，会自己封包，包的长度，避免粘包
  CDir Dir;
  // 打开文件
  //   bool OpenDir(const char *in_DirName,const char *in_MatchStr,const unsigned int in_MaxCount=10000,const bool bAndChild=false,bool bSort=false);
  if (false == Dir.OpenDir(starg.clientpath, starg.matchname, 1000, starg.andchild, false))
  {
    // 日志记录
    logfile.Write("读取目录失败\n");
    return false;
  }

  int delayed = 0; // 未收到对端确认报文的文件数量。
  int buflen = 0;  // 用于存放strrecvbuffer的长度。

  while (true)
  {

    // 复位
    memset(strsendbuffer, 0, sizeof(strsendbuffer));
    memset(strrecvbuffer, 0, sizeof(strrecvbuffer));

    // 遍历目录中的每个文件，调用ReadDir()获取一个文件名。
    if (Dir.ReadDir() == false)
      break; // 放一个文件信息，还有该文件，文件名，大小，修改时间

    SNPRINTF(strsendbuffer, sizeof(strsendbuffer), 1000, "<filename>%s</filename><mtime>%s</mtime><size>%d</size>", Dir.m_FullFileName, Dir.m_ModifyTime, Dir.m_FileSize);

    logfile.Write("strsendbuffer=%s\n", strsendbuffer);
    // 发送文件信息过去
    if (false == TcpClient.Write(strsendbuffer))
    {
      logfile.Write("TcpClient.Write() failed.\n");
      return false;
    }

    // 发送内容：发送遵循了tlv格式，接受也遵循tlv格式，保证不会粘包，自动分包
    // 也会write,所以要定好协议
    if (SendFile(TcpClient.m_connfd, Dir.m_FullFileName, Dir.m_FileSize) == false)
    {
      logfile.Write("Send failed.\n");
      return false;
    }
    else
    {
      delayed++;
    }
    // int bufferlen=1024;
    // if (false == TcpRead(TcpClient.m_connfd, strrecvbuffer, &bufferlen, 0))
    // {

    //   return false;
    // }

    // AckMessage(strrecvbuffer);

    while (true)
    {
      memset(strrecvbuffer, 0, sizeof(strrecvbuffer));
      if (TcpRead(TcpClient.m_connfd, strrecvbuffer, &buflen, -1) == false)
        break;
      // logfile.Write("strrecvbuffer=%s\n",strrecvbuffer);

      // 删除或者转存本地的文件。
      delayed--;
      AckMessage(strrecvbuffer);
    }
  }

  // 继续接收对端的确认报文。
  // 
  while (delayed > 0)
  {
    memset(strrecvbuffer, 0, sizeof(strrecvbuffer));
    if (TcpRead(TcpClient.m_connfd, strrecvbuffer, &buflen,0) == false)
      break;
    // logfile.Write("strrecvbuffer=%s\n",strrecvbuffer);

    // 删除或者转存本地的文件。
    delayed--;
    AckMessage(strrecvbuffer);
  }
  return true;
}

// 发送文件
bool SendFile(const int sockfd, const char *filename, const int filesize)
{

  // 铜鼓循环和一个中间buffer里发送数据
  int onread = 0;
  char buffer[1000];  // 存放读取数据的buffer。
  int totalbytes = 0; // 从文件中已读取的字节总数。
  int nextbyte = 0;

  // 文件操作：打开i文件
  CFile file;
  if (file.Open(filename, "rb") == false)
  {
    logfile.Write("打开文件%s失败\n", filename);
    return false;
  }
  memset(buffer, 0, 1000);

  while (true)
  {
    if (totalbytes == filesize)
    {
      break;
    }
    /* code */
    nextbyte = filesize - totalbytes;
    if (nextbyte > 1000)
    {
      onread = 1000;
    }
    else
    {
      onread = filesize - totalbytes;
    }

    // writ文件到缓冲区 size_t Fread(void *ptr,size_t size);
    int bytes = file.Fread(buffer, onread);
    if (bytes == onread)
    {
      logfile.Write("读取文件数据成功\n");
    }
    else
    {
      logfile.Write("读取文件数据失败\n");
      return false;
    }
    // bool Writen(const int sockfd,const char *buffer,const size_t n);
    if (Writen(sockfd, buffer, onread) == false)
    {
      logfile.Write("向socket写入数据失败\n");
      return false;
    }
    totalbytes += onread;

    // 为下一次迭代准备
    memset(buffer, 0, 1000);
  }
  file.Close();
  return true;
}

bool AckMessage(const char *strrecvbuffer)
{
  char filename[301];
  char result[11];

  memset(filename, 0, sizeof(filename));
  memset(result, 0, sizeof(result));

  GetXMLBuffer(strrecvbuffer, "filename", filename, 300);
  GetXMLBuffer(strrecvbuffer, "result", result, 10);

  // 如果服务端接收文件不成功，直接返回。
  if (strcmp(result, "ok") != 0)
    return false;

  // ptype==1，删除文件。
  if (starg.ptype == 1)
  {
    if (REMOVE(filename) == false)
    {
      logfile.Write("REMOVE(%s) failed.\n", filename);
      return false;
    }
  }

  // ptype==2，移动到备份目录。
  if (starg.ptype == 2)
  {
    // 生成转存后的备份目录文件名。
    char bakfilename[301];
    STRCPY(bakfilename, sizeof(bakfilename), filename);
    UpdateStr(bakfilename, starg.clientpath, starg.clientpathbak, false);
    if (RENAME(filename, bakfilename) == false)
    {
      logfile.Write("RENAME(%s,%s) failed.\n", filename, bakfilename);
      return false;
    }
  }

  return true;
}